﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Globalization;
using LostLib.Programs.Properties;
using System.Reflection;

namespace LostLib.Programs
{
	partial class AssemblyVersion : IFormattable, IEquatable<AssemblyVersion>
	{
		public bool Installed
		{
			get
			{
				var gac = GlobalAssemblyCache.GetAssemblies(true).Select(assembly => assembly.FullName);
				var name = this.FullName.FullName;
				return gac.Contains(name);
			}
		}

		public byte[] PublicKey
		{
			get
			{
				return Convert.FromBase64String(this.PublicKeyString);
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("PublicKey");

				this.PublicKeyString = Convert.ToBase64String(value);
			}
		}

		public AssemblyName FullName
		{
			get
			{
				var result = new AssemblyName();
				result.Name = this.AssemblyName;
				result.CultureInfo = new CultureInfo(this.Culture);
				result.ProcessorArchitecture = (ProcessorArchitecture)Enum.Parse(typeof(ProcessorArchitecture), this.ProcessorArchitecture);
				result.Version = this.Version;
				result.SetPublicKey(this.PublicKey);
				return result;
			}

			set
			{
				if (value == null)
					throw new ArgumentNullException("FullName");

				this.Culture = value.CultureInfo.ToString();
				this.AssemblyName = value.Name;
				this.ProcessorArchitecture = value.ProcessorArchitecture.ToString();
				this.SetVersion(value.Version);
			}
		}

		/// <summary>
		/// Determines whether this instance and another AssemblyVersion represents the same version. 
		/// </summary>
		/// <param name="other">An AssemblyVersion.</param>
		/// <returns>True if instances represent the same version.</returns>
		public bool Equals(AssemblyVersion other)
		{
			var version = other as AssemblyVersion;
			if (version == null) return false;
			return
				this.VersionString.Equals(version.VersionString);
		}

		/// <summary>
		/// Determines whether this instance and another object represents the same version. 
		/// </summary>
		/// <param name="obj">An object.</param>
		/// <returns>True if instances represent the same version.</returns>
		public override bool Equals(object obj)
		{
			var version = obj as AssemblyVersion;
			if (version == null) return false;
			return
				this.VersionString.Equals(version.VersionString);
		}
		/// <summary>
		/// Returns hash code for AssemblyVersion.
		/// </summary>
		/// <returns>Hash code.</returns>
		public override int GetHashCode()
		{
			return VersionString.GetHashCode();
		}

		/// <summary>
		/// Determines whether a local copy of version exists.
		/// </summary>
		public bool Cached
		{
			get
			{
				return File.Exists(LocalFileName);
			}
		}

		/// <summary>
		/// Gets a full file name for version local copy.
		/// </summary>
		public string LocalFileName
		{
			get { return Path.Combine(Settings.Default.AssemblyCache, VersionString) + ".dll"; }
		}

		/// <summary>
		/// Returns all resolved dependancies of this version.
		/// </summary>
		public IEnumerable<AssemblyVersion> ResolvedDependancies
		{
			get
			{
				foreach (var dependency in VersionDependancies)
				{
					if (dependency.DependencyType != "newer") throw new NotSupportedException(dependency.DependencyType);

					var installed = dependency.AssemblyInfo.InstalledVersion;
					if ((installed != null) && (installed.Version >= dependency.Version)) yield return installed;
				}
			}
		}
		/// <summary>
		/// Determines whether current verion is built-in.
		/// </summary>
		public bool IsBuiltIn
		{
			get
			{
				if (!VirtualAssembly) return false;

				return VersionDependancies.Count == 0;
			}
		}

		internal static AssemblyVersion Parse(XmlNode versionNode)
		{
			var ver = new Version(versionNode.Attributes["version"].Value);
			var processor = versionNode.Attributes["processor"].Value;
			var publicKey = versionNode.Attributes["publicKey"].Value;
			var size = int.Parse(versionNode.Attributes["size"].Value, CultureInfo.InvariantCulture);
			bool virt = bool.Parse(versionNode.Attributes["virtual"].Value);
			var uri = versionNode.Attributes["uri"].Value;
			var culture = versionNode.Attributes["culture"] == null
				? CultureInfo.InvariantCulture
				: new CultureInfo(versionNode.Attributes["culture"].Value, true);

			var version = new AssemblyVersion() {
				Uri = uri,
				SizeKiB = size,
				VirtualAssembly = virt,
				Version = ver,
				ProcessorArchitecture = processor,
				PublicKeyString = publicKey,
				Culture = culture.ToString(),
			};

			version.SetVersion(ver);

			version.VersionString = versionNode.Attributes["string"] != null
					? versionNode.Attributes["string"].Value
					: version.ToString(null, CultureInfo.InvariantCulture);


			return version;
		}

		public Version Version
		{
			get
			{
				return Build <0
					? new Version(Major, Minor)
					:( Revision < 0
					? new Version(Major, Minor, Build)
					: new Version(Major, Minor, Build, Revision));

			}
			set
			{
				SetVersion(value);
			}
		}

		private void SetVersion(Version value)
		{
			if (value == null)
				throw new ArgumentNullException("Version");

			Major = value.Major;
			Minor = value.Minor;
			Build = value.Build;
			Revision = value.Revision;
		}

		#region IFormattable Members
		/// <summary>
		/// Gets AssemblyVersion's string representation.
		/// </summary>
		/// <returns>String representation of AssemblyVersion.</returns>
		public override string ToString()
		{
			return string.Format(CultureInfo.CurrentCulture, "{0}={1}",
				AssemblyName,
				Version);
		}
		/// <summary>
		/// Gets AssemblyVersion's formatted string representation.
		/// </summary>
		/// <param name="format">Format string.</param>
		/// <param name="formatProvider">Format provider.</param>
		/// <returns>String representation of AssemblyVersion.</returns>
		public string ToString(string format, IFormatProvider formatProvider)
		{
			return //VersionString == null
				//? 
				  string.Format(formatProvider, "{0}={1}",
					AssemblyName,
					Version);
			//: VersionString;
		}

		#endregion
	}
}
